package com.bwie.service.impl;

import com.bwie.common.constants.RedissonConstants;
import com.bwie.common.constants.TokenConstants;
import com.bwie.common.domain.*;
import com.bwie.common.domain.request.LottoListReq;
import com.bwie.common.domain.request.LottoLogListReq;
import com.bwie.common.domain.request.LottoRuleAddReq;
import com.bwie.common.domain.request.RuleLogListReq;
import com.bwie.common.domain.response.DrawRes;
import com.bwie.common.domain.response.RuleLogRes;
import com.bwie.common.exceptions.BizExceptions;
import com.bwie.common.service.RedissonService;
import com.bwie.common.utils.JwtUtils;
import com.bwie.common.utils.StringUtils;
import com.bwie.mapper.LottoMapper;
import com.bwie.service.LottoService;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDate;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;

/**
 * @className: LottoServiceImpl
 * @Description: TODO
 * @version: v1.８.0
 * @author: 汪菲阳
 * @date: 2025/8/26 17:46
 */
@Service
public class LottoServiceImpl implements LottoService {
    @Autowired
    private LottoMapper lottoMapper;
    @Autowired
    private HttpServletRequest request;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedissonService redissonService;
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public List<Lotto> lottoList(LottoListReq lottoListReq) {
        return lottoMapper.lottoList(lottoListReq);
    }

    @Override
    public List<LottoLog> lottoLogList(LottoLogListReq lottoLogListReq) {
        return lottoMapper.lottoLogList(lottoLogListReq);
    }

    @Override
    @Transactional
    public DrawRes sweepstake(Integer lottoId) {
        //获取抽奖人id
        User userInfo = getUserInfo();
        if (userInfo == null){
            throw new BizExceptions(1008,"用户未登录");
        }
        //获取登录人id
        Integer userId = userInfo.getUserId();
        try {
            //拿锁（循环+重试）分布式锁
            redissonService.lock(RedissonConstants.INSERT_PRODUCT_STOCK,lottoId,
                    RedissonConstants.TIME_EXPIRE,
                    RedissonConstants.RETRY_TIMES,RedissonConstants.RETRY_INTERVAL);
//分布式锁
//        String lockKey="lotto:draw" + lottoId + ":" + userId;
//        int retries = 0;
//        while (!Boolean.TRUE.equals(
//                redisTemplate.opsForValue().setIfAbsent(lockKey,"1", Duration.ofSeconds(30)))){
//            if (++retries > 10){
//                throw new BizExceptions(1009,"系统繁忙，请稍候再试");
//            }
//            try {
//                Thread.sleep(50);//稍等
//            } catch (InterruptedException e) {
//                Thread.currentThread().interrupt();
//                throw new RuntimeException(e);
//            }
//        }
            //查询活动信息
            //根据前端传的id来查询抽奖（活动）信息
            Lotto lotto=lottoMapper.selectLottoInfo(lottoId);
            //判断活动的状态，如果为1则已失效
            if (lotto == null || lotto.getDicLottoState() == 1){
                throw new BizExceptions(1010,"活动已失效");
            }
            //查询用户剩余积分
            Integer leftPoints=lottoMapper.selectIntergrateByUserId(userId);
            //比较抽奖所需积分和用户剩余积分，如果用户剩余积分小于抽奖所需积分则抛异常
            if (leftPoints < lotto.getConsumePoints()){
                throw new BizExceptions(1011,"您的积分不足");
            }

            //计算可抽次数并缓冲到Redis，随后自减1
            Integer canDrowTimes=leftPoints / lotto.getConsumePoints();
            //创建一个key，用来记录抽奖次数
            String countKey="drow:" + lottoId + ":" +userId;
            //存入抽奖次数
            redisTemplate.opsForValue().set(countKey,canDrowTimes);
            //自减1
            redisTemplate.opsForValue().decrement(countKey,1);
            Integer leftTimes=(Integer) redisTemplate.opsForValue().get(countKey);

            /**
             * 7.抽奖若中奖则提示用户抽中了某物品
             * 8.若抽奖没有抽中则提示用户没有中奖
             */
            DrawRes drawRes = new DrawRes();
            //存入剩余抽奖次数
            drawRes.setCount(leftTimes);

            //核心抽奖：生成0-1随机数，匹配奖牌概率
            double luckyNumber = ThreadLocalRandom.current().nextDouble();
            //根据随机概率来查询数据库的概率，大于等于
            RuleLogRes ruleLogRes=lottoMapper.selectPrizeInfo(lottoId,luckyNumber);

            //封装抽奖日志
            LottoLog lottoLog = new LottoLog();
            lottoLog.setUserId(userId);
            lottoLog.setLottoLogRemarks("抽奖");
            lottoLog.setLottoLogTime(new Date());
            lottoLog.setUserPhone(userInfo.getUserPhone());
            lottoLog.setUserWechat(userInfo.getUserWechat());
            lottoLog.setLottoId(lottoId);
            lottoLog.setDicTypeIdLotto(2);

            //如果规则表为空，则未抽中奖品
            if (ruleLogRes==null){
                drawRes.setMessage("很遗憾，您未抽中奖牌，还剩"+drawRes.getCount()+"次");
                //中奖类型
                lottoLog.setWinningPrize(0);
            }else {
                //抽中奖则修改日志
                lottoLog.setWinningPrize(1);
                //中奖名称
                lottoLog.setRuleName(ruleLogRes.getRuleName());
                handleNoWinCase(ruleLogRes,drawRes,countKey,lottoLog);
            }
            //根据抽奖积分和id修改用户的积分
            lottoMapper.updUserIntegral(lotto.getConsumePoints(),userId);

            //发送mq （如果发送失败，只打印日志）
            try {
                rabbitTemplate.convertAndSend("addLottoLog",lottoLog);
            } catch (AmqpException e) {
                System.out.println("发送失败："+lottoLog);
                e.printStackTrace();
            }

            return drawRes;
        } finally {
            //释放锁
            redissonService.unlock(RedissonConstants.INSERT_PRODUCT_STOCK,lottoId);
        }
    }

    /**
     * 处理中奖逻辑
     * @param ruleLogRes 抽中的奖品
     * @param drawRes 返回前端的对象
     * @param countKey
     * @param lottoLog 抽奖日志
     */
    private void handleNoWinCase(RuleLogRes ruleLogRes, DrawRes drawRes, String countKey, LottoLog lottoLog) {
        //判断仓库是否还有奖品
        if (ruleLogRes.getWinningNum()<=0 && StringUtils.isNull(ruleLogRes.getWinningNum())){
            throw new BizExceptions(1012,"仓库奖牌不足");
        }
        drawRes.setMessage("您已抽中"+ruleLogRes.getWinningPrize()+"该奖品，还剩余"+drawRes.getCount()+"次抽奖机会");

        //如果抽中的奖品是积分就修改用户的积分余额
        if (ruleLogRes.getRuleName()=="积分"){
            //获取用户信息
            User userInfo = getUserInfo();
            //通过用户id和抽中积分进行修改用户积分
            //用户id
            Integer userId = userInfo.getUserId();
            //用户抽中的积分
            Integer ingetral = Integer.valueOf(ruleLogRes.getRuleName());
            lottoMapper.updUserIntegral(ingetral,userId);
            //修改中奖类型
            lottoLog.setWinningPrize(2);
        }
        //如果抽中的是奖品 则给奖品数量减一
        Integer prizeNum=lottoMapper.subtractNumByRuleId(ruleLogRes.getRuleId());
        //判断奖品数量是否等于0
        if (prizeNum==0){
            throw new BizExceptions(1013,"奖品库存不足,活动已结束");
        }
        //修改日志
        lottoLog.setWinningPrize(1);
    }

    @Override
    @Transactional
    public void singIn(Integer userId) {
        //获取当前时间
        LocalDate today = LocalDate.now();
        //根据用户id和当前日期查询用户是否已签到
        Integer singInCount=lottoMapper.singInCount(userId,today);
        if (singInCount>0){
            throw new BizExceptions(1006,"您今天已签到");
        }
        //生成10-100的随机积分
        Integer gainePoints=generateRandomPoints();
        //更新用户总积分
        User user=lottoMapper.selectByUserId(userId);
        if (user == null){
            throw new BizExceptions(1007,"该用户不存在");
        }
        //计算积分---用户原本积分+签到积分
        Integer newTotalPoints=user.getUserIntegral() + gainePoints;
        lottoMapper.updPoints(userId,newTotalPoints);

        //记录积分日志
        PointsLog pointsLog = new PointsLog();
        pointsLog.setUserId(userId);
        pointsLog.setDicPointsType(1);//1.收入 2.支出
        pointsLog.setPonitsNum(gainePoints);
        pointsLog.setPonitsSource("签到");//积分来源
        lottoMapper.insertPointsLog(pointsLog);
    }

    @Override
    public List<RuleLogListReq> ruleLogList(Integer lottoId) {
        return lottoMapper.ruleLogList(lottoId);
    }

    @Override
    public void getUserIntegral(Integer consumePoints) {
        //获取抽奖人的id（登录人）
        Integer userId = getUserInfo().getUserId();
        //根据抽奖人的id查询用户的剩余积分
        Integer userIntegral=lottoMapper.getUserIntegral(consumePoints,userId);
        //判断如果积分小于抽奖所需积分则抛异常
        if (userIntegral<consumePoints){
            throw new BizExceptions(1014,"您的积分不足");
        }
    }

    @Override
    public List<Dic> getDic(String dicType) {
        return lottoMapper.getDic(dicType);
    }

    @Override
    public void addLotto(LottoRuleAddReq lottoRuleAddReq) {
        //拿到抽奖信息
        Lotto lotto = lottoRuleAddReq.getLotto();
        //拿到规则信息
        List<RuleLog> ruleLogList = lottoRuleAddReq.getRuleLogList();
        //添加抽奖
        lottoMapper.addLotto(lotto);
        //添加抽奖规则中间表
        //给中间表中的数据赋值
        ruleLogList.forEach(rule->{
            rule.setLottoId(lotto.getLottoId());
        });
        lottoMapper.addRuleLog(ruleLogList);
    }

    @Override
    public List<Rule> ruleList() {
        return lottoMapper.ruleList();
    }

    /**
     * 获取抽奖人id（登录人）
     */
    private User getUserInfo(){
        String token = request.getHeader(TokenConstants.TOKEN);
        //获取userKey
        String userKey = JwtUtils.getUserKey(token);
        User user = (User) redisTemplate.opsForValue().get(TokenConstants.LOGIN_TOKEN_KEY + userKey);
        //判断用户信息是否为空
        if (user == null){
            throw new BizExceptions(1013,"请先登录");
        }
        return user;
    }

    /**
     * 签到随机积分
     * @return
     */
    private Integer generateRandomPoints(){
        Random random = new Random();
        //nextInt(n)生成0到n-1的随机数，所以+10的确最小值为10
        return random.nextInt(100 - 10 + 1) + 10;
    }
}
